#include "precompiled/pchkrayserkraw.h"

#include "kraw/krawapp.h"

#include "kraw/kraw3d.h"
#include "kraw/kraw3dmodel.h"
#include "kraw/kraw3dmaterial.h"
#include "kraw/kraw3dinput.h"

#include "kernel/ntimeserver.h"
#include "kernel/nscriptserver.h"

//------------------------------------------------------------------------------

class FlyCamInput : public Kraw3DInput
{
public:

    FlyCamInput()
    {
        this->KeyEvent( "forward", KEY_W, true );
        this->KeyEvent( "backward", KEY_S, true );
        this->KeyEvent( "left", KEY_A, true );
        this->KeyEvent( "right", KEY_D, true );
        this->KeyEvent( "up", KEY_UP, true );
        this->KeyEvent( "down", KEY_DOWN, true );

        this->KeyEvent( "faster", KEY_ADD, true );
        this->KeyEvent( "slower", KEY_SUBTRACT, true );

        this->KeyEvent( "reset", KEY_SPACE, true );
        this->KeyEvent( "exit", KEY_ESCAPE, true );
    }

protected:

    FlyCamInput( const FlyCamInput& );
    void operator=( const FlyCamInput& );
};

//------------------------------------------------------------------------------

KrawApp::KrawApp() :
    ref3DServer("/sys/servers/gfx")
{
}

//------------------------------------------------------------------------------

Model* KrawApp::LoadModel( const char* materialPath, const char* modelPath )
{
    Kraw3DMaterial* pMaterial = Kraw3DMaterial::Create( materialPath );
    if ( !pMaterial ) return 0;

    Kraw3DModel* pModel = Kraw3DModel::Create( modelPath );
    if ( !pModel ) return 0;

    void* pModelData = pMaterial->Load( pModel );
    if ( !pModelData ) return 0;

    Model& model = this->models.At( this->models.Size() );
    model.material.pMaterialData = pMaterial;
    model.pModelData = pModelData;

    return &model;
}

//------------------------------------------------------------------------------

void KrawApp::UnloadModel( Model* model )
{
    n_assert( model );
    n_assert( model->pModelData );
    n_assert( model->material.pMaterialData );

    Kraw3DMaterial* pMaterial = (Kraw3DMaterial*) model->material.pMaterialData;
    pMaterial->Unload( model->pModelData );
    model->pModelData = 0;
}

//------------------------------------------------------------------------------

bool KrawApp::Open()
{
    bIsDone = false;

    //open display
    this->display.width = 640;
    this->display.height = 480;
    this->display.left = 0;
    this->display.top = 0;
    this->display.title = "Krayser Kraw Display";

    if ( !this->ref3DServer->OpenDisplay( this->display ) ) return false;

    //load all materials
    nAutoRef<nScriptServer> refScriptServer( "/sys/servers/script" );
    nString result;
    if ( !refScriptServer->RunScript( "home:kraw/materials.lua", result ) ) return false;

    //load models and materials
    this->LoadModel( "materials/fixed", "proj:libs/system/meshes/triangle.n3d2" );
    //this->LoadModel( "materials/phong", "proj:libs/system/meshes/triangle.n3d2" );
    this->LoadModel( "materials/phong", "proj:libs/system/meshes/shape_torus.n3d2" );

    //setup a camera view
    if ( !this->ref3DServer->LoadView( this->view ) ) return false;

    //create world objects (or load from XML or n2 file)
    //this->refScene = kernel->Load( ".../scene.n2" );

    //create camera view(s)
    //camera or scene probe
    //view associated to that camera

    FlyCamInput* input = n_new( FlyCamInput );
    this->input = input;

    //TODO- move to FlyCamInput
    this->speed = .2f;//units/sec
    this->yaw = 0.f;
    this->pitch = 0.f;

    return true;
}

//------------------------------------------------------------------------------

void
KrawApp::Close()
{
    //this->material->Unload( this->model.pModelData );
    for ( Model* model = this->models.Begin(); !this->models.Empty(); model = this->models.Begin() )
    {
        this->UnloadModel( model );
        this->models.Erase( model );
    }

    this->ref3DServer->UnloadView( this->view );

    this->ref3DServer->CloseDisplay( this->display );

    Kraw3DModel::Cleanup();
    Kraw3DMaterial::Cleanup();

    n_delete( this->input );

    //TODO-  shutdown camera view(s)
}

//------------------------------------------------------------------------------

void
KrawApp::Tick( float fTime )
{
    this->ref3DServer->Trigger();

    //rotate the model
    if ( !this->models.Empty() )
    {
        for ( int index = 0; index < this->models.Size(); ++index )
        {
            Model& model = this->models[index];
            //Model& model = this->models[0];
            nTime time = float(nTimeServer::Instance()->GetTime());
            model.transform.seteulerrotation( vector3(0.f, float(time), 0.f) );
        }
    }

    //advance the simulation, update world state

    //interpret input and perform actions
    this->input->Tick( fTime );

    if ( this->input->GetState( "exit" ) ) this->bIsDone = true;

    //TODO- move this to FlyCamInput::Tick()
    float fElapsedTime = float(nTimeServer::Instance()->GetFrameTime());
    if ( this->input->GetState( "reset" ) )
    {
        this->eye.set( 0.0f, 3.0f,-5.0f );
        this->yaw = this->pitch = 0.f;
    }

	// Compute directional vectors from euler angles
	float cosX = cosf(this->yaw), sinX = sinf(this->yaw);
    float cosY = cosf(this->pitch), sinY = sinf(this->pitch);
	vector3 dx(cosY, 0, sinY);
	vector3 dy(-sinX * sinY,  cosX, sinX * cosY);
	vector3 dz(-cosX * sinY, -sinX, cosX * cosY);

	vector3 dir(0, 0, 0);
    if ( input->GetState( "left" ) ) dir -= dx;
	if ( input->GetState( "right" ) ) dir += dx;
	if ( input->GetState( "down" ) ) dir -= dy;
	if ( input->GetState( "up" ) ) dir += dy;
	if ( input->GetState( "backward" ) ) dir -= dz;
	if ( input->GetState( "forward" ) ) dir += dz;

    if ( input->GetState( "faster" ) ) this->speed *= 1.5f;
    if ( input->GetState( "slower" ) ) this->speed *= 0.7f;

	if ( dir.lensquared() > 0 )
    {
        dir.norm();
        this->eye += dir * fElapsedTime * speed;
        this->view.transform.settranslation( this->eye );
        this->view.transform.seteulerrotation( vector3(this->pitch,this->yaw,0) );
        //view matrix is the inverse of the camera world matrix
        matrix44 invView(this->view.transform.getmatrix());
        invView.invert();
        this->view.transform.setmatrix(invView);
	}
    //TODO- get the view matrix from the input
}

//------------------------------------------------------------------------------

void
KrawApp::Draw()
{
    //perform culling, render objects, etc.
    //TODO- get view matrix from the input
    if ( this->ref3DServer->BeginRender( this->display ) )
    {
        //set camera and viewport
        this->ref3DServer->DrawView( this->view );

        //render all models
        for ( Model* model = this->models.Begin(); model != this->models.End(); ++model )
        {
            Kraw3DMaterial* material = (Kraw3DMaterial*) model->material.pMaterialData;
            material->SetWorldMatrix( model->transform.getmatrix() );
            material->Render( model->pModelData );
        }

        //end rendering
        this->ref3DServer->EndRender();
    }
}
